#ifndef XG_JAVALOADER_H
#define XG_JAVALOADER_H
/////////////////////////////////////////////////////////////////////
#include <jni.h>

#include "../stdx/std.h"

typedef jint(*PFunCreateJavaVM)(JavaVM**, void**, void*);
typedef jint(*PFunGetCreatedJavaVM)(JavaVM**, jsize, jsize*);

class JavaLoader : public Object
{
public:
	JavaVM* jvm;
	bool created;
	JavaVMOption vmcfg[1];
	JavaVMInitArgs vmargs;

public:
	static string CppString(JNIEnv* env, jstring str)
	{
		string res;

		if (str)
		{
			const char* ptr = env->GetStringUTFChars(str, NULL);

			if (ptr)
			{
				res = ptr;
				env->ReleaseStringUTFChars(str, ptr);
			}
		}

		return res;
	}
	static jstring JavaString(JNIEnv* env, const string& str)
	{
		return env->NewStringUTF(str.c_str());
	}

public:
	JavaLoader()
	{
		jvm = NULL;
		created = false;

		memset(&vmargs, 0, sizeof(vmargs));
	}
	~JavaLoader()
	{
		close();
	}

public:
	void close()
	{
		if (created && jvm) jvm->DestroyJavaVM();

		jvm = NULL;
		created = false;
	}
	bool canUse() const
	{
		return jvm ? true : false;
	}
	bool init(JNIEnv* env)
	{
		close();

		CHECK_FALSE_RETURN(env->GetJavaVM(&jvm) == JNI_OK);

		vmargs.version = env->GetVersion();

		return true;
	}
	bool init(const string& classpath = "", int version = 0)
	{
		close();
		
#ifdef JNI_VERSION_1_8
		if (version == 0) version = JNI_VERSION_1_8;
#endif

#ifdef JNI_VERSION_1_7
		if (version == 0) version = JNI_VERSION_1_7;
#endif

#ifdef JNI_VERSION_1_6
		if (version == 0) version = JNI_VERSION_1_6;
#endif
		static string dllpath;

		if (dllpath.empty())
		{
			string path;
			vector<string> vec;

			path = Process::GetEnv("JAVA_HOME");

			CHECK_FALSE_RETURN(path.length() > 0);

#ifdef XG_LINUX
			CHECK_FALSE_RETURN(stdx::FindFile(vec, path, "libjvm.so*") > 0);
#else
			CHECK_FALSE_RETURN(stdx::FindFile(vec, path, "jvm.dll*") > 0);
#endif
			dllpath = vec[0];
		}

		sp<DllFile> dll = DllFile::Get(dllpath);

		CHECK_FALSE_RETURN(dll);

		jsize len = 0;
		JNIEnv* env = NULL;
		PFunCreateJavaVM CreateJavaVM = NULL;
		PFunGetCreatedJavaVM GetCreatedJavaVM = NULL;

		CHECK_FALSE_RETURN(dll->read(CreateJavaVM, "JNI_CreateJavaVM"));
		CHECK_FALSE_RETURN(dll->read(GetCreatedJavaVM, "JNI_GetCreatedJavaVMs"));

		GetCreatedJavaVM(&jvm, sizeof(jvm), &len);

		if (jvm)
		{
			CHECK_FALSE_RETURN(jvm->GetEnv((void**)(&env), version) == JNI_OK);

			return true;
		}

		if (classpath.empty())
		{
			vmcfg[0].optionString = (char*)("-Djava.class.path=.");
		}
		else
		{
			static string param = "-Djava.class.path=" + classpath;

			vmcfg[0].optionString = (char*)(param.c_str());
		}

		vmargs.options = vmcfg;
		vmargs.version = version;
		vmargs.nOptions = ARR_LEN(vmcfg);
		vmargs.ignoreUnrecognized = JNI_TRUE;

		CHECK_FALSE_RETURN(CreateJavaVM(&jvm, (void**)(&env), &vmargs) == JNI_OK);

		return created = true;
	}
	SmartBuffer call(JNIEnv* env, const string& clazz, const string& name, SmartBuffer param, bool object, int* code)
	{
		int len = 0;
		SmartBuffer res;
		jclass cls = NULL;
		jbyte* data = NULL;
		jobject obj = NULL;
		jmethodID mid = NULL;
		jbyteArray arr = NULL;
		jbyteArray rsp = NULL;

		if ((cls = env->FindClass(clazz.c_str())) == NULL)
		{
			if (code) *code = XG_NOTFOUND;

			goto __END__;
		}

		if ((arr = env->NewByteArray(param.size())) == NULL) goto __END__;

		env->SetByteArrayRegion(arr, 0, param.size(), (jbyte*)(param.str()));

		if (object)
		{
			if ((mid = env->GetMethodID(cls, "<init>", "()V")) == NULL) goto __END__;
 
			if ((obj = env->NewObject(cls, mid)) == NULL) goto __END__;
 
			if ((mid = env->GetMethodID(cls, name.c_str(), "([B)[B")) == NULL)
			{
				if (code) *code = XG_NOTFOUND;

				goto __END__;
			}

			if ((rsp = (jbyteArray)env->CallObjectMethod(obj, mid, arr)) == NULL) goto __END__;
		}
		else
		{
			if ((mid = env->GetStaticMethodID(cls, name.c_str(), "([B)[B")) == NULL)
			{
				if (code) *code = XG_NOTFOUND;

				goto __END__;
			}

			if ((rsp = (jbyteArray)env->CallObjectMethod(cls, mid, arr)) == NULL) goto __END__;
		}

		if ((len = env->GetArrayLength(rsp)) <= 0) goto __END__;

		if ((data = env->GetByteArrayElements(rsp, NULL)) == NULL) goto __END__;

		memcpy(res.malloc(len), data, len);

		env->ReleaseByteArrayElements(rsp, data, 0);

		if (code) *code = XG_OK;

	__END__:
		if (rsp) env->DeleteLocalRef(rsp);
		if (arr) env->DeleteLocalRef(arr);
		if (obj) env->DeleteLocalRef(obj);
		if (cls) env->DeleteLocalRef(cls);

		return res;
	}
	SmartBuffer call(const string& clazz, const string& name, SmartBuffer param, bool object = false, int* code = NULL)
	{
		JNIEnv* env = NULL;

		if (code) *code = XG_SYSERR;

		if (jvm->AttachCurrentThread((void**)(&env), NULL) < 0) return SmartBuffer();
	
		SmartBuffer res = call(env, clazz, name, param, object, code);
	
		jvm->DetachCurrentThread();

		return res;
	}
	SmartBuffer taskQueueCall(const string& clazz, const string& name, SmartBuffer param, bool object = false, int* code = NULL)
	{
		JNIEnv* env = NULL;

		if (code) *code = XG_SYSERR;
	
		if (jvm->GetEnv((void**)(&env), vmargs.version) < 0)
		{
			auto detach = [=](){
				jvm->DetachCurrentThread();
			};

			if (TaskQueue::Instance()->trySetDestroyFunc(detach))
			{
				if (jvm->AttachCurrentThread((void**)(&env), NULL) < 0)
				{
					TaskQueue::Instance()->cancelDestroyFunc();

					return SmartBuffer();
				}
			}
			else
			{
				return SmartBuffer();
			}
		}

		return call(env, clazz, name, param, object, code);
	}
};
/////////////////////////////////////////////////////////////////////
#endif